Skip to main content

Fahlunn ปี 2014

·3 mins

อธิบายตัวแอปพลิเคชัน #

ฟ้าลั่นเป็นแอปพลิเคชันที่ช่วยตัวแทนประกันชีวิตนำเสนอแผนการขายได้ชัดเจนมากขึ้น โดยผ่านการใช้รูปภาพกราฟ และการปรับตัวเลขต่างๆโดยใช้แค่การลากนิ้ว ใช้การพิมพ์ keyboard แค่ตอนกรอกชื่อเท่านั้นเอง จากความยืดหยุ่นใรการปรับแผนได้อย่างอิสระ ก็สามารถทำให้ตัวแทนปิดการขายได้เร็วมากขึ้น อีกทั้งยังได้แผนความคุ้มครองที่เหมาะกับลูกค้าคนนั้นจริงๆเพราะเขาเลือกด้วยตัวเอง

โจทย์สำคัญ #

  • ตัวแอปถูกออกแบบมาเพื่อเป็น salekit และ presentation ในตัว ทำให้เรื่องความสวยงามและการนำเสนอข้อมูลที่กระชับเป็นเรื่องสำคัญมาก มีการ custom UIKit หลายจุดมากๆ
  • มีการบันทึกข้อมูลลูกค้าที่ผู้ใช้งานทำการเสนอขายไปด้วยเพื่อเป็นประวัติและข้อมูลในการวางแผนการขายต่อไป
  • เนื่องจากแผนประกันมีการเปลี่ยนแปลงอยู่ตลอดเวลา การคำนวณหรือเงื่อนไขต่างๆในแอปจำเป็นต้อง update ตัวเองได้ทันที โดยไม่ต้องผ่าน App Store ซึ่งต้องใช้เวลาในการอนุมัติ 3-7 วัน (อ้างอิงจากปี 2014) เนื่องจากลูกค้าของเราต้องออกไปขายทุกวัน

การพัฒนา feature เพื่อตอบโจทย์สำคัญ #

ด้านการนำเสนอเราใช้ภาพที่สวยงามจากทีม design จำนวนมาก มาใช้ร่วมกับ UIKit ไม่ว่าจะเป็น

  • วงล้อเลือกอายุ ที่ประยุกต์ใช้ UIScrollView มาบังหน้า UIImageView ที่เป็นรูปจานเลข หลังจากนั้นเราก็หาความยาวรอบรูปด้วยสูตร l = 2π * r แล้วนำมากำหนดเป็น contentSize.width ของ scrollview หลังจากนั้นจึงสั่งให้ UIImageView ด้านหลังหมุนไปตามการ scroll และทำการดักตำแหน่ง x ของ scroll เพื่อนำมาคำนวณหาว่า ณ จุดกึ่งกลางตอนนี้คืออายุเท่าใด
Fahlunn
  • ภูเขา เป็นเสมือนสัญลักษณ์ของแอปนี้ หน้านี้จะเป็นการ custom เอา UIView ที่มีการใส่ UIPanGestureRecognizer เข้าไปด้วยเพื่อตรวจจับลากการลากนิ้วของผู้ใช้งานขึ้นลง โดยที่ขนาดความสูงเหล่านี้จะส่งผลต่อค่าความคุ้มครองของประกันแต่ละตัวที่ผู้ใช้เลือก
Fahlunn
  • กราฟความคุ้มครอง ส่วนนี้ถึงจะเป็นการใช้ภาพ static ที่จะถูกโหลดมาแสดงตามแผนประกันที่ลูกค้าเลือก แต่ตัวเลขปีและเงินคืนก็เป็นส่วนที่ต้องใช้ UILabel และใช้การดักจับ touch บน UIView มาเพื่อหาตำแหน่งว่าแตะเลขตัวไหนอยู่เพื่อที่จะได้แสดง popover ขึ้นมาถูก (ที่ไม่ใช้ UIButton เพราะต้องการให้เอานิ้วลากได้เลย ไม่อยากให้เป็นเหมือนการกดปุ่ม)
Fahlunn
  • การ Print หรือ Export เอกสาร นอกจากการดูบนจอแล้ว ผู้ใช้สามารถสั่งพิมพ์หรือส่งอีเมล์ไฟล์สรุปในรูปแบบ PDF ก็ได้ โดยไฟล์นี้ถูกสร้างจาก UIView ขนาดใหญ่เท่ากระดาษ A4 (เพื่อป้องกันอาการภาพแตก) แล้ว render เป็นภาพหรือ PDF เพื่อนำไปใช้งานต่อตามที่ผู้ใช้ต้องการ
Fahlunn
  • เก็บประวัติการนำเสนอ แปลงเล่มกรมธรรม์ที่นำเสนอไปเก็บอยู่ใน plist ไฟล์ก่อนที่จะทำการ upload ไปเก็บไว้บน server ด้วยเพื่อป้องกันความผิดพลาดหากลูกค้าเผลอลบแอปทิ้ง ก็จะยังมีข้อมูลที่สำรองไว้กับเรา
Fahlunn
  • JavascriptCore เนื่องจากแผนประกันมีการ update อยู่ตลอด(ทุกเดือน) เพื่อไม่ให้งานขายของลูกค้าต้องสะดุดจากการรอ App Store อนุมัติ ผมจึงต้องเก็บ business logic ต่างๆ ทั้งการคำนวณเบี้ย หรือแม้แต่การกำหนดค่า max / min ของอนุสัญญาแต่ละตัวด้วย Javascript เพราะเราสามารถ js code ซึ่งเป็น text มา run บน iOS ได้แปลว่า ทุกครั้งที่มีการใช้แอปจะมีการตรวจสอบอยู่เสมอว่า core ของตัวคำนวณยังเป็น version ล่าสุดอยู่หรือไม่ ถ้าหากไม่ใช่ก็จะทำการ download ลงมาในรูปแบบ zip แล้ว unzip เก็บไว้ใน directory ของแอปเองเพื่อเป็นการ update ตัว business logic ทั้งหมด

ตัวอย่างการเขียน feature บางส่วน #

การสร้างภูเขา (mountain slider) #

  • สร้าง view โดยการกำหนดความสูง max ได้พร้อมกับมี closure block สำหรับทำงานเวลาที่มีการเปลี่ยนค่าของภูเขา
func createMainMountain() {
        let width = 360
        let height = 370
        let y = 768 - height
        let x = (1024/2) - (width/2)

        mainMountainView = MountainView(
            name: "mountain-main",
            maxHeight: 550,
            frame: CGRect(x: x, y: y, width: width, height: height),
            valueChanged:{(value: CGFloat, name:String) in
                print("\(name): \(value)")
            }
        )

        self.mountainScrollView.addSubview(mainMountainView)
    }
  • ภายใน MountainView จะมี PanGestureRecognizer คอยตรวจสอบการลากนิ้วบน view เพื่อจะยืดหดความสูงตาม
@objc func didPan(sender: UIPanGestureRecognizer) {
    guard let mountainView = sender.view else { return }
    if sender.state == .began {
         startPosition = sender.location(in: mountainView)
    }

    if sender.state == .began || sender.state == .changed {
        translation = sender.translation(in: mountainView)
        let newY = self.frame.origin.y + translation.y

        if let superview = self.superview {
            let newHeight = superview.frame.size.height - newY
            if newHeight >= originalHeight && newHeight <= maxHeight {
                self.frame = CGRect(x: self.frame.origin.x, y: newY, width: self.frame.size.width, height: newHeight)
                self.setMountainValue()
                if let block = valueDidChanged {
                    block(self.mountainValue, name)
                }
            }
        }
    }

    sender.setTranslation(CGPoint(x: 0.0, y: 0.0), in: mountainView)
    mountainImageView.frame.size = self.frame.size
}
  • ความสูงของ view จะถูกเอามาเปรียบเทียบเพื่อหาค่าจาก 0 - 100 เพื่อนำไปแปลงเป็นค่าของภูเขาแต่ละลูก (หรือความคุ้มครองของประกันชีวิตนั่นเอง)
func setMountainValue() {
    let max = self.maxHeight - self.originalHeight
    let difference = self.frame.height - self.originalHeight
    mountainValue = round((difference / max) * 100)
    self.mountainLabel.text = String(format: "%.0f", mountainValue)
}
Fahlunn

FLMountainView #

https://gitlab.com/clonezer/flmountainviewcontroller

การใช้ Javascript Core ในการ evaluate javascript code #

เนื่องจากบน iOS SDK มี JavascriptCore Framework ให้เราได้เรียกใช้อยู่แล้ว เราจึงไม่จำเป็นต้องติดตั้งอะไรเพิ่ม โดยคำสั่งหลักๆที่มักเจอบ่อยๆก็คือ evaluateScript() ก็คือการ complie ตัว javascript code ให้พร้อมใช้งานได้แล้ว เช่น

import JavaScriptCore

func getJSContext(with jsCode: String) -> JSContext {
    guard let context = JSContext() else { return }
    //เพื่อให้ Javascript รู้จัก Object ของฝั่ง swift เราสามารถประกาศได้ดังนี้
    context.setObject(Book.self, forKeyedSubscript: "book" as Book)
    context.setObject(InsuranceList.self, forKeyedSubscript: "insuranceList" as InsuranceList)
    context.setObject(Assured.self, forKeyedSubscript: "assured" as Assured)
    context.setObject(Payer.self, forKeyedSubscript: "payer" as Payer)
    context.setObject(Insurance.self, forKeyedSubscript: "insurance" as Insurance)
    context.setObject(Insurance.self, forKeyedSubscript: "mainInsurance" as Insurance)
    context.setObject(Insurance.self, forKeyedSubscript: "rider" as Insurance)

    context.evaluateScript(jsCode)
    return context
}

หลังจากได้ JSContext แล้วหากเราต้องการคำนวณเราก็สามารถส่ง object เข้าไปใน javascript function ได้เลย เช่น ตัวอย่างการส่ง mainInsurance(แผนประกันหลัก) และ assured(ผู้เอาประกัน) เข้าไปใน function getMultiplierRate เพื่อที่จะได้เบี้ยของแผนประกันออกมา

func getMainInsuranceRate(mainContext: JSContext, mainInsurace: Insurance, assured: Assured) -> JSValue? {
    guard
        let getMultiplierRate = mainContext.objectForKeyedSubscript("getMultiplierRate")
        let value = getMultiplierRate.call(withArguments: [mainInsurance, assured])
    else { return nil }

    return value
}

วิธีนี้มีประโยชน์มากนอกจากจะทำให้เรา update business logic ที่ซับซ้อนได้โดยไม่ต้องผ่าน Store แล้ว เรายังสามารถนำ Javascript Code ชุดเดียวกันไปใช้กับ Web หรือ platform อื่นๆก็ได้ ทำให้ตัว core การคำนวณถูกพัฒนาแค่ครั้งเดียวบน Javascript ไม่ต้องเขียนหลายรอบ